//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/.
// 			VisualNovelToolkit		     /_/_/_/_/_/_/_/_/_/.
// Copyright ©2013 - Sol-tribe.	/_/_/_/_/_/_/_/_/_/_/_/_/_/.
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace ViNoToolkit{

	/// <summary>
	/// Script interpreter.
	/// </summary>
	public class ScriptInterpreter {

		// ---------- static members --------------------.
		static private Dictionary<string,string> m_MacroMap = new Dictionary<string,string>();
		static private int m_CurrLabelId;	
		static private string m_CurrLabel;
		static public GameObject m_CurrentNode;

		// ---------- private members --------------------.
		protected System.Text.StringBuilder m_StrBuild;
		protected Tokenizer m_TagTokenizer;
		protected string m_CurrentLine;
		protected bool m_IsBrachet;
		protected Hashtable m_AttrTable;
		
		/// <summary>
		/// To a scenario script.
		/// </summary>
		/// <returns>The scenario script.</returns>
		/// <param name="nodeObject">Node object.</param>
		static public string ToScenarioScript( GameObject nodeObject ){
			BaseNode[] nodes =  nodeObject.GetComponentsInChildren<BaseNode>();
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			for(int i=0;i<nodes.Length;i++){
				if( nodes[ i ].enabled ){
					nodes[ i ].ToScenarioScript( ref sb );	
				}
			}		
	//		Debug.Log ( " ---- KIRIKIRI Script ----" );
	//		Debug.Log( sb.ToString() );
			return sb.ToString();
		}
		
		/// <summary>
		/// Analyzes the variable.
		/// </summary>
		/// <param name="byteList">Byte list.</param>
		/// <param name="str">Var Name string.</param>
		static public void AnalyzeVar( List<byte> byteList , string str ){
			bool readVarNameMode = false;
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			System.Text.StringBuilder varSb = new System.Text.StringBuilder();
			for(int i=0;i<str.Length;i++){
				if( str[ i ] == '$' ){
					readVarNameMode = true;
					ByteCodeScriptTools.AddStringCode( byteList , sb.ToString() );
					sb.Length = 0;
				}
				else if( str[ i ] == ' ' ){
					if( readVarNameMode ){
						readVarNameMode = false;
						ByteCodeScriptTools.AddVarCode( byteList , varSb.ToString() );	
						varSb.Length = 0;	
					}
					else{	
						sb.Append( str[ i ] );								
					}
				}
				else{
					if( readVarNameMode ){
						varSb.Append( str[ i ] ); 
					}
					else{
						sb.Append( str[ i ] );
					}
				}
			}

			if( sb.Length > 0 ){
				ByteCodeScriptTools.AddStringCode( byteList , sb.ToString() );
			}	
		}
		
		virtual public int HideMessage( List<byte> byteList ){ return 0;}

		virtual public int WaitTouch( List<byte> byteList ){ return 0; }

		virtual public int EndLinkTag( List<byte> byteList ){ return 0; }

		// ch is '[' or '@'.
		int ParseMessageTag( string tag , ref List<byte> byteList ){
			int pos = 0;
	//		Debug.Log( "TAG:" + tag + " Found.");
			switch( tag ){
				// Single Token Tag.
				case "s":			pos = Stop( byteList );			break;
				case "cm":			pos = CM( byteList );			break;
				case "er":			pos = ER( byteList );			break;
				case "l":			pos = WaitTouch( byteList );	break;
				case "p":			pos = Page( byteList );			break;
				case "r":			pos = Reline( byteList );		break;
				case "endlink":		pos = EndLinkTag( byteList );	break;
				case "hidemessage":	pos = HideMessage( byteList );	break;
				// Attribute Exists Tags.
				default :		pos = AnalyzeTag( byteList );	break;
			}
			return pos;
		}

		int AddSingleOpcode( List<byte> byteList , byte opcode ){
			byteList.Add( opcode );
			return ParseUntilCloseTag();
		}

		int Stop( List<byte> byteList ){
			return AddSingleOpcode( byteList , Opcode.STOP );
		}

		int CM( List<byte> byteList ){
			int pos = AddSingleOpcode( byteList , Opcode.CM );

	// Automatic Stub Savepoint.
	#if true		
			ByteCodeScriptTools.AddNodeCode( byteList , m_CurrLabel + "_" + m_CurrLabelId.ToString() );
			m_CurrLabelId++;
	#endif		
			return pos;
		}

		int ER( List<byte> byteList ){
			return AddSingleOpcode( byteList , Opcode.ER );
		}

		int Reline( List<byte> byteList ){
			m_StrBuild.Append( "\n" );
			return AddSingleOpcode( byteList , Opcode.BR );
		}

		int Page( List<byte> byteList ){
			int pos = WaitTouch( byteList );
			byteList.Add( Opcode.ER );
	// Automatic Stub Savepoint.
	#if true		
			ByteCodeScriptTools.AddNodeCode( byteList , m_CurrLabel + "_" + m_CurrLabelId.ToString() );
			m_CurrLabelId++;
	#endif				
			return pos;
		}

		/// <summary>
		/// Interpret the specified script string.
		/// </summary>
		/// <param name="script">Script.</param>
		public List<byte> Interpret( string script ){
			List<byte> byteList = new List<byte>();

			// Set Default Text Message Target.
			string[] lines = script.Split( "\n"[0] );
			m_StrBuild = new System.Text.StringBuilder();
			for( int i=0;i<lines.Length;i++){
				m_CurrentLine = lines[ i ];
				// Replace Macro of this Line.
				if( m_MacroMap.ContainsKey( m_CurrentLine ) ){
					lines[ i ] = m_MacroMap[ m_CurrentLine ];
	//				Debug.Log( "Replaced Macro Line:"+i.ToString() + "to :" + m_MacroMap[ m_CurrentLine ] );
					m_CurrentLine = lines[ i ];
				}

				for( int k=0;k<lines[ i ].Length;k++){				
					char ch = lines[ i ][ k ];
/*					
					if( m_CurrentLine.Contains("$") && m_CurrentLine.Contains("=")  ){
						string[] strs = lines[ i ].Split( '=' );
						string varName = strs[ 0 ];
						string val = strs[ 1 ];
						varName = varName.Replace( " " , "" );
						varName = varName.Replace( "$" , "" );
						val = val.Replace( " " , "" );					
	//					Debug.Log( "varName:" + varName );
	//					Debug.Log( "var:" + val );
						ByteCodeScriptTools.AddAssignStringCode( byteList , varName , val );
						break;
					}
//*/
					// Parse Label *.
					if( ch == '*' ){				
	//					Debug.Log( "Label(*) Found.");
						k++;
						string temp = lines[ i ].Substring( k , lines[i].Length - k );
						ParseLabelLine( temp , byteList );
						break;
					}
					// Parse Tag.
					else if( ch == '[' || ch == '@' ){
						m_IsBrachet = ( ch == '[') ? true : false;
						k++;
						m_TagTokenizer = new Tokenizer( lines[ i ] , k );
						m_TagTokenizer.ChangeReadModeGLOBAL();

						string tag = m_TagTokenizer.GetToken();
						tag = tag.ToLower();
						
						k = m_TagTokenizer.position;

						// Parse Tag.
						// m_AttrTable is Set.
						k = ParseMessageTag( tag , ref byteList );

						KAGTagParser.Parse( tag , m_AttrTable , byteList );

					}	
					// Comment.
					else if( ch == ';' ){
						k++;
						string temp = lines[ i ].Substring( k , lines[i].Length - k );
						Debug.Log( "KAGScript Comment : " + temp );
						break;					
					}
					// Define.
					else if( ch == '#' ){
						k++;
						string temp = lines[ i ].Substring( k , lines[i].Length - k );
						string[] pair = temp.Split( ',' );
						m_MacroMap[ pair[ 0 ] ] = pair[ 1 ];
						break;					
					}
					else{
						m_StrBuild.Append( ch );
					}
				}			
			}	

	//		foreach(string key in m_MacroMap.Keys ){
	//			Debug.Log( "Macro:"+key + "=>" + m_MacroMap[ key ] );	
	//		}
	//		byteList.Add( Opcode.STOP );
			return byteList;
		}
		
		/// <summary>
		/// Analyzes the tag.
		/// </summary>
		/// <returns>The tag.</returns>
		/// <param name="byteList">Byte list.</param>
		int AnalyzeTag( List<byte> byteList ){
			m_TagTokenizer.ChangeReadModeTAG();
			int returnPos = 0;
			m_AttrTable = new Hashtable();
			while( ! m_TagTokenizer.IsOutOfRangePos() ){
				string attr = m_TagTokenizer.GetToken();
				// End ?.
				if( m_IsBrachet ){
					if( attr == "]" ){
						returnPos = m_TagTokenizer.position;
						returnPos--;
						break;
					}
				}

				string token =  m_TagTokenizer.GetToken();
				if( token == "=" ){

					m_TagTokenizer.ChangeReadMode( Tokenizer.ReadMode.READ_ATTRIBUTE );

					string val = m_TagTokenizer.GetToken();

					m_TagTokenizer.ChangeReadMode( Tokenizer.ReadMode.TAG );

					// Trim ".
					val = val.Replace( "\"" , "" );

					m_AttrTable[ attr ] = val;
				}
/*				else if( token == "\"" ){
					Debug.Log( "\"Found." );
					if( Tokenizer.readMode ==  Tokenizer.ReadMode.TAG ){
						m_TagTokenizer.ChangeReadMode( Tokenizer.ReadMode.TEXT );
					}
					else if( Tokenizer.readMode ==  Tokenizer.ReadMode.TEXT ){
						m_TagTokenizer.ChangeReadMode( Tokenizer.ReadMode.TAG );					
					}
				}
	//*/			
				else{
					Debug.LogError( "Tag Parse Error." );
				}
			}

			// not brachet mode.
			if( ! m_IsBrachet ){
				returnPos = m_CurrentLine.Length;
			}		
			m_TagTokenizer.ChangeReadModeGLOBAL();

			return returnPos;
		}

		protected int ParseUntilCloseTag(){
			int returnPos = 0;
			// Read until ] is found.
			while( ! m_TagTokenizer.IsOutOfRangePos() ){
				string token = m_TagTokenizer.GetToken();
				returnPos = m_TagTokenizer.position;
				if( m_IsBrachet ){
					if( token == "]" ){
						returnPos--;
						break;
					}
				}
			}
			// not brachet mode.
			if( ! m_IsBrachet ){
				returnPos = m_CurrentLine.Length;
			}					
			return returnPos;
		}

		protected void ParseLabelLine( string line , List<byte> byteList ){
			string[] temp2 = line.Split( '|' );
			ByteCodeScriptTools.AddNodeCode( byteList , temp2[ 0 ] );
			m_CurrLabel = temp2[ 0 ];	
			m_CurrLabelId = 0;

	#if UNITY_EDITOR
			if( ! Application.isPlaying ){
				m_CurrentNode = GameObject.Find( temp2[0] );
				if( m_CurrentNode == null ){
					m_CurrentNode = new GameObject( temp2[0] );
				}
				// Destroy all attached Components.
				else{
					BaseNode[] nodes =  m_CurrentNode.GetComponents<BaseNode>();
					for(int i=0;i<nodes.Length;i++){
						GameObject.DestroyImmediate( nodes[ i ] );
					}
				}
				m_CurrentNode.AddComponent<BaseNode>();
			}
	#endif		
		}

	}

}